Skip to content

feat: email multiple recipients#14098

Open
juleswg23 wants to merge 8 commits intoquarto-dev:mainfrom
juleswg23:email-mulitple-recipients
Open

feat: email multiple recipients#14098
juleswg23 wants to merge 8 commits intoquarto-dev:mainfrom
juleswg23:email-mulitple-recipients

Conversation

@juleswg23
Copy link
Contributor

@juleswg23 juleswg23 commented Feb 23, 2026

Description

This PR adds support for dynamic email recipients in the email format for Posit Connect. Recipients can now be computed programmatically via Python or R code and specified in multiple formats.

Key Features

  • Dynamic computation: Recipients can be generated via Python/R code execution
  • Multiple specification patterns:
    • Static inline recipients
      • comma separated text in metadata div
      • new-line separated
    • Dynamic, code-computed recipients:
      • Conditional inline recipients (code block output in metadata div)
      • Metadata attribute pattern (using write_yaml_metadata_block() in R or equivalent in Python)
  • Tests: Test coverage for all patterns in both Python and R

v2 Metadata Structure

We now return v2 which looks identical to the changes in #13882, but with a recipients field:

{
    "emails": [
        {
            "attachments": [],
            "body_html": "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\"> <!-- utf-8 works for most cases -->\n<meta name=\"viewport\" content=\"width=device-width\"> <!-- Forcing initial-scale shouldn't be necessary -->\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"> <!-- Use the latest (edge) version of IE rendering engine -->\n<meta name=\"x-apple-disable-message-reformatting\">  <!-- Disable auto-scale in iOS 10 Mail entirely -->\n<meta name=\"format-detection\" content=\"telephone=no,address=no,email=no,date=no,url=no\"> <!-- Tell iOS not to automatically link certain text strings. -->\n<meta name=\"color-scheme\" content=\"light\">\n<meta name=\"supported-color-schemes\" content=\"light\">\n<!-- What it does: Makes background images in 72ppi Outlook render at correct size. -->\n<!--[if gte mso 9]>\n<xml>\n<o:OfficeDocumentSettings>\n<o:AllowPNG/>\n<o:PixelsPerInch>96</o:PixelsPerInch>\n</o:OfficeDocumentSettings>\n</xml>\n<![endif]-->\n<style>\nbody {\nfont-family: Helvetica, sans-serif;\nfont-size: 14px;\n}\n.content {\nbackground-color: white;\n}\n.content .message-block {\nmargin-bottom: 24px;\n}\n.header .message-block, .footer message-block {\nmargin-bottom: 12px;\n}\nimg {\nmax-width: 100%;\n}\n@media only screen and (max-width: 767px) {\n.container {\nwidth: 100%;\n}\n.articles, .articles tr, .articles td {\ndisplay: block;\nwidth: 100%;\n}\n.article {\nmargin-bottom: 24px;\n}\n}\n</style>\n</head>\n<body style=\"background-color:#f6f6f6;font-family:Helvetica, sans-serif;color:#222;margin:0;padding:0;\">\n<table width=\"85%\" align=\"center\" class=\"container\" style=\"max-width:1000px;\">\n<tr>\n<td style=\"padding:24px;\">\n<div class=\"header\" style=\"font-family:Helvetica, sans-serif;color:#999999;font-size:12px;font-weight:normal;margin:0 0 24px 0;text-align:center;\">\n</div>\n<table width=\"100%\" class=\"content\" style=\"background-color:white;\">\n<tr>\n<td style=\"padding:12px;\">\n<p>First email HTML content.</p>\n</td></tr>\n</table>\n<div class=\"footer\" style=\"font-family:Helvetica, sans-serif;color:#999999;font-size:12px;font-weight:normal;margin:24px 0 0 0;\">\n<p>This message was generated on 2026-01-16 16:15:12.</p>\n\n<p>If HTML documents are attached, they may not render correctly when viewed in some email clients. For a better experience, download HTML documents to disk before opening in a web browser.</p>\n</div>\n</td>\n</tr>\n</table>\n</body>\n</html>\n",
            "body_text": "Text version of first email.\n",
            "email_id": 1,
            "recipients": [
                "a@example.com",
                "b@example.com",
            ],
            "send_report_as_attachment": false,
            "subject": "First Email Subject",
            "suppress_scheduled": false
        },
        {
            "attachments": [],
            "body_html": "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\"> <!-- utf-8 works for most cases -->\n<meta name=\"viewport\" content=\"width=device-width\"> <!-- Forcing initial-scale shouldn't be necessary -->\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"> <!-- Use the latest (edge) version of IE rendering engine -->\n<meta name=\"x-apple-disable-message-reformatting\">  <!-- Disable auto-scale in iOS 10 Mail entirely -->\n<meta name=\"format-detection\" content=\"telephone=no,address=no,email=no,date=no,url=no\"> <!-- Tell iOS not to automatically link certain text strings. -->\n<meta name=\"color-scheme\" content=\"light\">\n<meta name=\"supported-color-schemes\" content=\"light\">\n<!-- What it does: Makes background images in 72ppi Outlook render at correct size. -->\n<!--[if gte mso 9]>\n<xml>\n<o:OfficeDocumentSettings>\n<o:AllowPNG/>\n<o:PixelsPerInch>96</o:PixelsPerInch>\n</o:OfficeDocumentSettings>\n</xml>\n<![endif]-->\n<style>\nbody {\nfont-family: Helvetica, sans-serif;\nfont-size: 14px;\n}\n.content {\nbackground-color: white;\n}\n.content .message-block {\nmargin-bottom: 24px;\n}\n.header .message-block, .footer message-block {\nmargin-bottom: 12px;\n}\nimg {\nmax-width: 100%;\n}\n@media only screen and (max-width: 767px) {\n.container {\nwidth: 100%;\n}\n.articles, .articles tr, .articles td {\ndisplay: block;\nwidth: 100%;\n}\n.article {\nmargin-bottom: 24px;\n}\n}\n</style>\n</head>\n<body style=\"background-color:#f6f6f6;font-family:Helvetica, sans-serif;color:#222;margin:0;padding:0;\">\n<table width=\"85%\" align=\"center\" class=\"container\" style=\"max-width:1000px;\">\n<tr>\n<td style=\"padding:24px;\">\n<div class=\"header\" style=\"font-family:Helvetica, sans-serif;color:#999999;font-size:12px;font-weight:normal;margin:0 0 24px 0;text-align:center;\">\n</div>\n<table width=\"100%\" class=\"content\" style=\"background-color:white;\">\n<tr>\n<td style=\"padding:12px;\">\n<p>Second email HTML content.</p>\n</td></tr>\n</table>\n<div class=\"footer\" style=\"font-family:Helvetica, sans-serif;color:#999999;font-size:12px;font-weight:normal;margin:24px 0 0 0;\">\n<p>This message was generated on 2026-01-16 16:15:12.</p>\n\n<p>If HTML documents are attached, they may not render correctly when viewed in some email clients. For a better experience, download HTML documents to disk before opening in a web browser.</p>\n</div>\n</td>\n</tr>\n</table>\n</body>\n</html>\n",
            "body_text": "Text version of second email.\n",
            "email_id": 2,
            "recipients": [
                "c@example.com",
                "d@example.com",
            ],
            "send_report_as_attachment": false,
            "subject": "Second Email Subject",
            "suppress_scheduled": false
        }
    ],
    "rsc_email_version": 2
}

### Example Usage

Static recipients:
```markdown
::: {.recipients}
alice@example.com
bob@example.com
charlie@example.com
:::

Dynamic recipients (inline Python/R):

```{python}
# the code can be substituted with an R equivalent.
recipients = get_team_emails() 
```

:::{.email}
email contents

:::{.recipients}
`{python} recipients`
:::

:::

Dynamic recipients (metadata Python):

```{python}
# | output: asis
import quarto
recipients = get_team_emails() 
quarto.write_yaml_metadata_block(recipients = recipients)
```

:::{.email recipients=recipients}
email contents
:::

Dynamic recipients (metadata R):

```{r}
# | output: asis
library("quarto-r")
recipients <- get_team_emails()
quarto-r::write_yaml_metadata_block(recipients = recipients)
```

:::{.email recipients=recipients}
email contents
:::

Closes #14023

Checklist

I have (if applicable):

  • filed a contributor agreement.
  • referenced the GitHub issue this PR closes
  • updated the appropriate changelog in the PR
  • ensured the present test suite passes
  • added new tests
  • created a separate documentation PR in Quarto's website repo and linked it to this PR

@posit-snyk-bot
Copy link
Collaborator

posit-snyk-bot commented Feb 23, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@juleswg23 juleswg23 marked this pull request as ready for review February 25, 2026 19:09
@juleswg23
Copy link
Contributor Author

Despite the failing tests (which seem out of my control) this PR is ready for review @jonkeane @cscheid

Comment on lines +68 to +75
-- Parse recipients from inline code output or plain text
-- Supports multiple formats:
-- 1. Python list: ['a', 'b'] or ["a", "b"]
-- 2. R vector: "a" "b" "c"
-- 3. Comma-separated: a, b, c
-- 4. Line-separated: a\nb\nc
-- Returns an empty array if parsing fails
function parse_recipients(recipient_str)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach here is parsing through various printed versions of list/vectors in Python + R and then extract the strings inside of them which (presumably) works (I haven't checked the tests yet, so maybe there actually are corner cases or bugs or something, but either way...).

What if, instead, we do the opposite: look for email addresses in the string and ignore everything else. My lua is not strong ™️ but Claude came up with the following:

recipients = {}
  for email in string.gmatch(recipient_str, "[%w%._%+%-]+@[%w%.%-]+%.%w+") do   
    table.insert(recipients, email)
  end                                                                           
  if #recipients > 0 then
    return recipients
  end

No need to detect [...] vs list(...) vs c(...), no comma splitting, no quote
stripping (ASCII or curly), no bracket removal, no trimming. The email pattern
cuts through all of that because the surrounding syntax (quotes, brackets,
commas, spaces) will never match the email pattern — it naturally rejects all
of it.

What do you think of doing it that way?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

email: conditional recipients in rendered email metadata

3 participants